/*
 * Copyright (c) 2011, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.wso2.carbon.registry.lifecycle.accenture.sample.utils;

import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.impl.builder.StAXOMBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.registry.api.RegistryException;
import org.wso2.carbon.registry.api.Resource;
import org.wso2.carbon.registry.core.jdbc.handlers.RequestContext;
import org.wso2.carbon.registry.lifecycle.accenture.sample.ServiceConstant;

import javax.xml.namespace.QName;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

/**
 * TODO: This class should not be to be static as seperate maps should be maintained for each Service resource
 */
public class SubscriptionUtil {
    private static final Log log = LogFactory.getLog(SubscriptionUtil.class);

    /**
     * <i>susbcriptionMap</i> contains all the email ids required during the notification process in state transition.
     * There's a email list associated with each ID catagory called DEVELOPER_ID, ARCHITECT_ID, TESTER_ID etc
     */
    public HashMap<String, List<String>> subscriptionMap;

    public static final String DEVELOPER_ID = "ServiceDeveloper".toLowerCase();
    public static final String ARCHITECT_ID = "SOAArchitect".toLowerCase();
    public static final String TESTER_ID = "ServiceTester".toLowerCase();
    public static final String MANAGER_ID = "SOAManager".toLowerCase();
    public static final String CONSUMER_ID = "ServiceConsumer".toLowerCase();
    public static final String ENTRY_ELEMENT = "entry";
    public static final String OVERVIEW_ELEMENT = "overview";
    public static final String NAMESPACE = "http://www.wso2.org/governance/metadata";

    private List<String> developerEmailList = new ArrayList<String>();
    private List<String> architectEmailList = new ArrayList<String>();
    private List<String> testerEmailList = new ArrayList<String>();
    private List<String> managerEmailList = new ArrayList<String>();
    private List<String> consumerEmailList = new ArrayList<String>();

    public SubscriptionUtil() {
        if (subscriptionMap == null) {
            initMap();
        }
    }

    public void initMap() {
        subscriptionMap = new HashMap();
        subscriptionMap.put(DEVELOPER_ID, developerEmailList);
        subscriptionMap.put(ARCHITECT_ID, architectEmailList);
        subscriptionMap.put(TESTER_ID, testerEmailList);
        subscriptionMap.put(MANAGER_ID, managerEmailList);
        subscriptionMap.put(CONSUMER_ID, consumerEmailList);
    }

    /**
     * This method get the <serviceMetaData xmlns="http://www.wso2.org/governance/metadata" /> content
     * and fill all the required email addresses to the <i>susbcriptionMap</i>
     * TODO: Proper way to impl this is to devide the impl based on the each child element
     *
     * @param serviceContent
     */
    private HashMap<String, List<String>> initSubscriptionMapFromServiceMetadata(OMElement serviceContent) {
        if (subscriptionMap == null) {
            initMap();
        }

        OMElement overviewElem = serviceContent.getFirstChildWithName(new QName(NAMESPACE, OVERVIEW_ELEMENT));

        OMElement devEmailElem = overviewElem.getFirstChildWithName(new QName(NAMESPACE, DEVELOPER_ID));
        if (devEmailElem != null) {
            if (developerEmailList.add(devEmailElem.getText())) {
                subscriptionMap.put(DEVELOPER_ID, developerEmailList);
            }
        } else {
            log.error("Please check element:" + DEVELOPER_ID + " exist in <serviceMetaData/>");
        }

        OMElement archEmailElem = overviewElem.getFirstChildWithName(new QName(NAMESPACE, ARCHITECT_ID));
        if (archEmailElem != null) {
            if (architectEmailList.add(archEmailElem.getText())) {
                subscriptionMap.put(ARCHITECT_ID, architectEmailList);
            }
        } else {
            log.error("Please check element:" + ARCHITECT_ID + " exist in <serviceMetaData/>");
        }

        OMElement testerEmailElem = overviewElem.getFirstChildWithName(new QName(NAMESPACE, TESTER_ID));
        if (testerEmailElem != null) {
            if (testerEmailList.add(testerEmailElem.getText())) {
                subscriptionMap.put(TESTER_ID, testerEmailList);
            }
        } else {
            log.error("Please check element:" + TESTER_ID + " exist in <serviceMetaData/>");
        }

        OMElement consumersElem = serviceContent.getFirstChildWithName(new QName(NAMESPACE, CONSUMER_ID));
        if (consumersElem != null) {
            for (Iterator<OMElement> entryElems = consumersElem.getChildrenWithName(new QName(NAMESPACE, ENTRY_ELEMENT)); entryElems.hasNext();) {
                OMElement entryElem = entryElems.next();

                try {
                    String consumerEmail = getConsumerEmailAddressFromConsumerEntry(entryElem.getText());
                    if (consumerEmailList.add(consumerEmail)) {
                        subscriptionMap.put(CONSUMER_ID, consumerEmailList);
                    }
                } catch (Exception e) {
                    log.error("Consumer email extraction logic failed.");
                }

            }
        } else {
            log.error("Please check element:" + CONSUMER_ID + " exist in <serviceMetaData/>");
        }

        return subscriptionMap;

    }

    /**
     * In the current service UI the consumer entry is as follows
     *   <table name="Consumers">
            <subheading>
                <heading>Consumer Name</heading><heading>E-mail</heading>
            </subheading>
            <field type="option-text" maxoccurs="unbounded">
                <name label="Consumers">Consumers</name>
                <values>
                    <value>None</value><value>Service Developer</value><value>Service Tester</value>
                </values>
            </field>
        </table>
     *
     * So there's option field. So once a new service is created, the <serviceMetaData/> is as follows.
     *  <consumers><entry>Service Developer:foo@bar.com</entry></consumers>
     *
     * So to extract the email address we need to split by ":".
     * This method manage this impl.
     * @param consumerEntry
     * @return
     */
    private String getConsumerEmailAddressFromConsumerEntry(String consumerEntry) throws Exception {
        String[] entries = consumerEntry.split(":");
        if (entries != null || entries.length != 0) {
            return entries[1];
        } else {
            String errorMsg = "There's a problem in the extraction logic in the consumer email extraction " +
                      "from <serviceMetaData/>";
            log.error(errorMsg);
            throw new Exception(errorMsg);
        }
    }

    public List<String> getArchitectEmailList() {
        return subscriptionMap.get(ARCHITECT_ID);
    }

    /**
     * Add the given email address to list that manage the SOAManager's emails
     *
     * @param managerEmail
     * @return
     */
    private List<String> setManagerEmailList(String managerEmail) {
        if (subscriptionMap == null) {
            initMap();
        }

        if (managerEmailList.add(managerEmail)) {
            subscriptionMap.put(MANAGER_ID, managerEmailList);
        }

        return subscriptionMap.get(MANAGER_ID);
    }

    /**
     * This method generate the subscriptionMap from the resource extracted from the regsitry context
     *
     * @param requestContext
     * @return
     */
    public HashMap<String, List<String>> initSubscriptionMap(RequestContext requestContext) {
        Resource resource = requestContext.getResource();
        String requestServiceString = null;
        XMLStreamReader reader = null;
        try {
            byte[] content = (byte[]) resource.getContent();
            requestServiceString = new String(content);

        } catch (RegistryException e) {
            log.error(e.getLocalizedMessage(), e);
        }
        try {
            reader = XMLInputFactory.newInstance().createXMLStreamReader(new StringReader(requestServiceString));
        } catch (XMLStreamException e) {
            log.error(e.getLocalizedMessage(), e);
        }
        StAXOMBuilder builder = new StAXOMBuilder(reader);

        String managerEmail = resource.getProperty(ServiceConstant.CREATED_BY);
        if (managerEmail != null) {
            setManagerEmailList(managerEmail);
        } else {
            log.error("SOAManager's email address is not set as a property called \"" +
                      ServiceConstant.CREATED_BY + "\" for the particular service:" + resource.getPath());
        }

        return initSubscriptionMapFromServiceMetadata(builder.getDocumentElement());

    }

    public List<String> getEmailsForGivenRoles(List<String> roleList) {
        List<String> emailList = new ArrayList<String>();
        if (subscriptionMap == null) {
            return null;
        }
        for (String role : roleList) {
            String compatibleRoleName = prepareRoleNameCompatible(role.trim());
            if (subscriptionMap.get(compatibleRoleName) != null) {
                emailList.addAll(subscriptionMap.get(compatibleRoleName));
            } else {
                log.error("No email is declared for role:" + role + ".");
            }
        }

        return emailList;
    }

    /**
     * G-Reg service UI config maintains a set of emails for each role
     * Life-cycle configuration also maintains a set of subscriptions based on roles for each
     * promote/demote action in state transition.
     * So these role values are maintained at two places. They are G-Reg service UI config and
     * Life-cycle configuration.
     *
     * G-Reg service UI have its own way of storing these role names in the <serviceMetaData/>,
     * once we define those in the Service UI configuration.
     * eg - <field type="text" required="true">
               <name label="SOA Architect">Soaarchitect</name>
            </field>
     * is rectified in the <serviceMetaData/> as follows.
     *  eg - <soaarchitect>foo@bar.com</soaarchitect>
     *
     * So to avoid conflictions between G-Reg service UI config and Life-cycle config we define
     * attributes in the service UI config only in lowerCase.
     * But Life-Cycle config can maintain role name values as defined in the user-store to avoid
     * conflictions between other elements defined in the Life cycle configuration.
     *
     * So to enforce compatibility between service UI configuration and Life-cycle configuration
     * when accessing the roles in service UI configuration from values retrieved from Life-cycle
     * configuration we need to convert the role value to lower case.
     *
     * @param role
     * @return
     */
    private String prepareRoleNameCompatible(String role) {
        return role.toLowerCase();
    }
}
